/* save 20/12/05(土) 22:05:47 今回は 次回は 頂点の位置を始点からの相対高さで指定したい。 aがいくつだったら頂点のyを指定の値にできるか。 */ class App { constructor( canvasId ) { this.cc = document.getElementById( canvasId ).getContext( "2d" ); let cc = this.cc; cc.translate( cc.canvas.width / 2, cc.canvas.height /2 ); cc.scale( 1, -1 ); cc.tmp = { circle : function( x, y, r, strokeStyle, fillStyle ) { cc.beginPath(); cc.arc( x, y, r, 0, 6.28 ); cc.closePath(); if( typeof fillStyle !== "undefined" && fillStyle != null ) { cc.fillStyle = fillStyle; cc.fill(); } if( typeof strokeStyle !== "undefined" && strokeStyle != null ) { cc.strokeStyle = strokeStyle; cc.stroke(); } }, } this.graphPaper = new GraphPaper( function( x ) { return x * x } ); this.draw( this.cc ); this.anms = new Array(); switch( "key" ) { case "key": onkeydown = function( e ) { switch( e.which ) { case 32: this.frame(); break; case 65: //a this.graphPaper.a += 0.001; this.draw( this.cc ); break; case 90: //z this.graphPaper.a -= 0.001; this.draw( this.cc ); break; default: console.log( e.which ); } }.bind( this ); break; case "timer": default: setInterval( this.frame.bind( this ), 100 ); }//switch this.anms.push( { object : this.graphPaper, propertyName : "sy", sx : -100, ex : 100, step : 5, dir : -1, frame : function() { this.object[ this.propertyName ] += this.step * this.dir; //check. if( this.dir > 0 && this.object[ this.propertyName ] >= this.ex || this.dir < 0 && this.object[ this.propertyName ] <= this.sx ) { this.dir *= -1; } }, } ); this.anms.push( { object : this.graphPaper, propertyName : "ey", sx : -100, ex : 100, step : 5, dir : 1, frame : function() { this.object[ this.propertyName ] += this.step * this.dir; //check. if( this.dir > 0 && this.object[ this.propertyName ] >= this.ex || this.dir < 0 && this.object[ this.propertyName ] <= this.sx ) { this.dir *= -1; } }, } ); this.anms.push( { object : this.graphPaper, propertyName : "sx", sx : -200, ex : 100, step : 5, dir : 1, frame : function() { this.object[ this.propertyName ] += this.step * this.dir; //check. if( this.dir > 0 && this.object[ this.propertyName ] >= this.ex || this.dir < 0 && this.object[ this.propertyName ] <= this.sx ) { this.dir *= -1; } }, } ); this.anms.push( { object : this.graphPaper, propertyName : "ex", sx : -100, ex : 200, step : 5, dir : -1, frame : function() { this.object[ this.propertyName ] += this.step * this.dir; //check. if( this.dir > 0 && this.object[ this.propertyName ] >= this.ex || this.dir < 0 && this.object[ this.propertyName ] <= this.sx ) { this.dir *= -1; } }, } ); }//constructor start() { this.runtype = "key"; switch( this.runtype ) { case "key": this.keyfunc = function( e ) { switch( e.which ) { case 32: this.frame(); break; case 65: //a this.graphPaper.a += 0.001; this.draw( this.cc ); break; case 90: //z this.graphPaper.a -= 0.001; this.draw( this.cc ); break; default: console.log( e.which ); return true; } e.stopPropagation(); e.preventDefault(); return false; }.bind( this ); addEventListener( "keydown", this.keyfunc ); break; case "timer": default: setInterval( this.frame.bind( this ), 100 ); }//switch } stop() { switch( this.runtype ) { case "key": removeEventListener( "keydown", this.keyfunc ); break; } } frame() { this.anms.map( anm => anm.frame() ); this.draw( this.cc ); } draw( cc ) { this.graphPaper.draw( cc ); //原点 if( 0 ) { cc.tmp.circle( 0, 0, 4, null, "red" ); cc.save(); cc.scale( 1, -1 ); cc.fillText( "原点", 12, 0 ); cc.restore(); } } } class GraphPaper { constructor() { this.f = null; this.sx = -100; this.sy = 10; this.ex = 100; this.ey = 50; this.axisX = 1; //放物線の頂点のx座標 this.numberOfPoints = 100; this.adjustY = null; this.a = -0.01; } //sxからexまでをnumberOfPoints分割したとき、 //分割のindex番目の、グラフ上のpx,pyを求める。 setIndex( index ) { this.index = index; this.px = this.sx + index * this.step; this.py = this.f( this.px ); } //放物線の式を更新 _update( a, sx, sy, ex, ey ) { this.f = function( x ) { return a * Math.pow( x, 2 ) + b * x + c; } //sxからexまでをnumberOfPoints分割するときの間隔 this.step = ( this.ex - this.sx ) / this.numberOfPoints; //グラフの位置調整 this.setIndex( 0 ); this.adjustY = this.sy - this.py; } // https://naop.jp/ensyu/1/kai3_2_3.html update( _a, sx, sy, ex, ey ) { this.maxY = Math.max( sy, ey ) + 40; //( 0, this.maxY ) //y=ax2+bx+c //maxY = a 0^2 + b 0x + c //maxY = c //c = maxY //( sx, sy ) //y=ax2+bx+c //sy = a sx^2 + b sx + c //sy = a sx^2 + b sx + maxY //a sx^2 + b sx = sy - maxY ...[1] //( ex, ey ) //y=ax2+bx+c //ey = a ex^2 + b ex + c //ey = a ex^2 + b ex + maxY //a ex^2 + b ex = ey - maxY ...[2] //[2]-[1] (bを消去する) // a ex^2 + b ex = ey - maxY //-)a sx^2 * rate + b sx * rate = ( sy - maxY ) * rate // a ex^2 - a sx^2 * rate + (bは消去) = ey - maxY - ( sy - maxY ) * rate let siki2anum = Math.pow( ex, 2 ); //ex^2 let siki2bnum = ex; //ex let siki2right = ey - this.maxY; //ey - maxY let siki1anum = Math.pow( sx, 2 ); //sx^2 let siki1bnum = sx; //sx let siki1right = sy - this.maxY; //sy - maxY let rate = siki2bnum / siki1bnum; siki1anum *= rate; //sx^2 * rate siki1bnum *= rate; //sx * rate (siki1bnum == siki2bnum) siki1right *= rate; //( sy - maxY ) * rate //bが消去されたのでaについて解く // a ex^2 - a sx^2 * rate + (bは消去) = ey - maxY - ( sy - maxY ) * rate // a ( ex^2 - sx^2 * rate ) = ( ey - maxY ) - ( ( sy - maxY ) * rate ) // a = ( ( ey - maxY ) - ( ( sy - maxY ) * rate ) ) / ( ex^2 - sx^2 * rate ) let resanum = siki2anum - siki1anum; //( ex^2 - sx^2 * rate ) let resright = siki2right - siki1right; //( ey - maxY ) - ( ( sy - maxY ) * rate ) let a = resright / resanum; //aがわかったので、bについて解く //a ex^2 + b ex = ey - maxY ...[2] //b ex = ( ey - maxY ) - a ex^2 //b = ( ( ey - maxY ) - a ex^2 ) / ex let b = ( siki2right - siki2anum * a ) / siki2bnum; let c = this.maxY; let choX = - b / ( 2 * a ); let choY = - a * Math.pow( b / ( 2 * a ), 2 ) + c; this.f = function( x ) { return a * Math.pow( x, 2 ) + b * x + c; } //sxからexまでをnumberOfPoints分割するときの間隔 this.step = ( this.ex - this.sx ) / this.numberOfPoints; //グラフの位置調整 this.setIndex( 0 ); this.adjustY = this.sy - this.py; } draw( cc ) { //画面クリア cc.clearRect( -cc.canvas.width / 2, -cc.canvas.height / 2, cc.canvas.width, cc.canvas.height ); //放物線の式を更新 this.update( this.a, this.sx, this.sy, this.ex, this.ey ); //放物線を描く for( let i = 0; i <= this.numberOfPoints; i++ ) { this.setIndex( i ); let y = this.py + this.adjustY; cc.tmp.circle( this.px, y, 2, null, "blue" ); } cc.fillStyle = "black"; //始点 cc.tmp.circle( this.sx, this.sy, 8, "blue" ); cc.save(); cc.translate( this.sx + 12, this.sy ); cc.scale( 1, -1 ); cc.fillText( "始点", 0, 0 ); cc.restore(); //終点 cc.tmp.circle( this.ex, this.ey, 8, "red" ); cc.save(); cc.translate( this.ex + 12, this.ey ); cc.scale( 1, -1 ); cc.fillText( "終点", 0, 0 ); cc.restore(); //3点目 cc.tmp.circle( 0, this.maxY, 4, null, "green" ); cc.save(); cc.translate( 12, this.maxY ); cc.scale( 1, -1 ); cc.fillText( "3点目", -32, -16 ); cc.restore(); //参考値 if( 1 ) { cc.fillStyle = "red"; cc.fillRect( -cc.canvas.width / 2, this.maxY, cc.canvas.width, 1 ); cc.save(); cc.translate( -cc.canvas.width / 2 + 16, this.maxY ); cc.scale( 1, -1 ); cc.fillText( "この高さに放物線の頂点を合わせたい。", 0, -3 ); cc.restore(); } } }